#pragma once
#ifndef MEM_CACHE_MANAGER_HEADER
#define MEM_CACHE_MANAGER_HEADER

#include<stdio.h>
#include<memory>
#include<queue>
#include<stack>
#include<mutex>
#include<condition_variable>

class CoreManager;

class MemCacheItem
{
public:
	MemCacheItem(std::shared_ptr<void>mem, const std::shared_ptr<CoreManager> manager)
	: m_mem(mem)
	, m_manager(manager)
	{
		//NOTHING
	}
	std::shared_ptr<void>	lockMem();
	std::shared_ptr<void>	lockPinnedMem();
	std::shared_ptr<void>	lockGpuMem();

private:
	size_t m_len;
	std::shared_ptr<void> m_mem;
	std::shared_ptr<void> m_pinnedMem;
	std::shared_ptr<void> m_gpuMem;
	//MemCacheItem need hard link to CoreManager
	//while CoreManager holds only weak link to MemCacheItem
	std::shared_ptr<CoreManager> m_manager;
	friend class CoreManager;
	void releasePinnedMem();
	void releaseGpuMem();
};

class MemAllocator
{
public:
	virtual void * alloc(size_t len) = 0;
};
class MemDeletor
{
public:
	virtual void free(void *) = 0;
};
class MemMover
{
public:
	virtual void move(void *dst, const void *src, size_t len) = 0;
};

class CoreManager
{
public:
	CoreManager(
		size_t cacheBytes,
		int memCacheSoftLimit,
		int memCacheHardLimit,
		int pinnedMemCacheSoftLimit,
		int pinnedMemCacheHardLimit,
		int gpuMemCacheSoftLimit,
		int gpuMemCacheHardLimit,
		std::shared_ptr<MemAllocator> memAllocator,
		std::shared_ptr<MemAllocator> pinnedMemAllocator,
		std::shared_ptr<MemAllocator> gpuMemAllocator,
		std::shared_ptr<MemDeletor> memDeletor,
		std::shared_ptr<MemDeletor> pinnedMemDeletor,
		std::shared_ptr<MemDeletor> gpuMemDeletor,
		std::shared_ptr<MemMover> mem2PinnedMemMover,
		std::shared_ptr<MemMover> pinned2GpuMemMover
	)
	: m_memCacheBytes(cacheBytes)
	, m_memCachesSoftLimit(memCacheSoftLimit)
	, m_memCachesHardLimit(memCacheHardLimit)
	, m_memCacheTotalCount(0)
	, m_pinnedMemCachesSoftLimit(pinnedMemCacheSoftLimit)
	, m_pinnedMemCachesHardLimit(pinnedMemCacheHardLimit)
	, m_pinnedMemCacheTotalCount(0)
	, m_gpuMemCachesSoftLimit(gpuMemCacheSoftLimit)
	, m_gpuMemCachesHardLimit(gpuMemCacheHardLimit)
	, m_gpuMemCacheTotalCount(0)
	, m_memAllocator(memAllocator)
	, m_pinnedMemAllocator(pinnedMemAllocator)
	, m_gpuMemAllocator(gpuMemAllocator)
	, m_memDeletor(memDeletor)
	, m_pinnedMemDeletor(pinnedMemDeletor)
	, m_gpuMemDeletor(gpuMemDeletor)
	, m_mem2PinnedMemMover(mem2PinnedMemMover)
	, m_pinned2GpuMemMover(pinned2GpuMemMover)
	{
	};
	
	~CoreManager();
private:
	//cache item len in bytes
	size_t m_memCacheBytes;
	int m_memCachesSoftLimit;
	int m_memCachesHardLimit;
	int m_pinnedMemCachesSoftLimit;
	int m_pinnedMemCachesHardLimit;
	int m_gpuMemCachesSoftLimit;
	int m_gpuMemCachesHardLimit;
	std::mutex m_memCachesMutex;
	std::mutex m_pinnedMemCachesMutex;
	std::condition_variable m_pinnedMemCachesCond;
	std::mutex m_gpuMemCachesMutex;
	std::condition_variable m_gpuMemCachesCond;
	//stack LILO, for hot memory recycling
	std::stack<void *> m_memFreeCaches;
	std::stack<void *> m_pinnedMemFreeCaches;
	std::stack<void *> m_gpuMemFreeCaches;
	int m_memCacheTotalCount;
	int m_pinnedMemCacheTotalCount;
	int m_gpuMemCacheTotalCount;
	std::shared_ptr<MemAllocator> m_memAllocator;
	std::shared_ptr<MemAllocator> m_pinnedMemAllocator;
	std::shared_ptr<MemAllocator> m_gpuMemAllocator;
	std::shared_ptr<MemDeletor> m_memDeletor;
	std::shared_ptr<MemDeletor> m_pinnedMemDeletor;
	std::shared_ptr<MemDeletor> m_gpuMemDeletor;
	std::shared_ptr<MemMover> m_mem2PinnedMemMover;
	std::shared_ptr<MemMover> m_pinned2GpuMemMover;

	enum DeletorType
	{
		DEL_TYPE_MEM,
		DEL_TYPE_PINNED_MEM,
		DEL_TYPE_GPU_MEM
	};
	friend class Deletor;
	void freeMem(void *ptr);
	void freePinnedMem(void *ptr);
	void freeGpuMem(void *ptr);
	class Deletor
	{
	public:
		void operator()(void *ptr);
		Deletor(const std::shared_ptr<CoreManager> manager, enum DeletorType type, std::shared_ptr<MemDeletor> fallbackDeletor)
		: manager(manager)
		, type(type)
		, fallbackDeletor(fallbackDeletor)
		{
			//NOTHING	
		};
	private:
		std::weak_ptr<CoreManager> manager;
		std::shared_ptr<MemDeletor> fallbackDeletor;
		enum DeletorType type;
	};


	std::mutex m_cacheItemsMutex;
	std::queue<std::weak_ptr<MemCacheItem>> m_cacheItems;

	friend class MemCacheItem;
	std::shared_ptr<void> allocPinnedMemory(const void *srcMem, std::shared_ptr<CoreManager> coreManager);
	std::shared_ptr<void> allocGpuMemory(const void *srcPinnedMem, std::shared_ptr<CoreManager> coreManager);

public:
	
	std::shared_ptr<MemCacheItem> alloc(std::shared_ptr<CoreManager> coreManager);

};

class MemCacheManager
{
public:
	MemCacheManager(
		size_t cacheBytes,
		int memCacheSoftLimit,
		int memCacheHardLimit,
		int pinnedMemCacheSoftLimit,
		int pinnedMemCacheHardLimit,
		int gpuMemCacheSoftLimit,
		int gpuMemCacheHardLimit,
		std::shared_ptr<MemAllocator> memAllocator,
		std::shared_ptr<MemAllocator> pinnedMemAllocator,
		std::shared_ptr<MemAllocator> gpuMemAllocator,
		std::shared_ptr<MemDeletor> memDeletor,
		std::shared_ptr<MemDeletor> pinnedMemDeletor,
		std::shared_ptr<MemDeletor> gpuMemDeletor,
		std::shared_ptr<MemMover> mem2PinnedMemMover,
		std::shared_ptr<MemMover> pinned2GpuMemMover
	)
	{
		m_coreManager = std::make_unique<CoreManager>(
			cacheBytes,
			memCacheSoftLimit,
			memCacheHardLimit,
			pinnedMemCacheSoftLimit,
			pinnedMemCacheHardLimit,
			gpuMemCacheSoftLimit, 
			gpuMemCacheHardLimit,
			memAllocator,
			pinnedMemAllocator,
			gpuMemAllocator,
			memDeletor,
			pinnedMemDeletor,
			gpuMemDeletor,
			mem2PinnedMemMover,
			pinned2GpuMemMover);
	};
	
	~MemCacheManager()
	{
		m_coreManager = nullptr;
	}

	std::shared_ptr<MemCacheItem> alloc()
	{
		return m_coreManager->alloc(m_coreManager);
	}
private:
	std::shared_ptr<CoreManager> m_coreManager;
};

#endif
